#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <fcntl.h>
#include <unistd.h>

#include "linked_list.h"
#include "semaphore.h"
#include "util.h"

seat_t *seat_header = NULL; // struct in seats.h
standby_t *standby_header = NULL;
m_sem_t *standby_mutex;
pthread_mutex_t seat_mutex = PTHREAD_MUTEX_INITIALIZER;
int standby_size = 0;

// seat_header, curr->id, curr->next are shared variables but they are never changed
// we don't need to protect them before using them

char seat_state_to_char(seat_state_t);

// list all the seat and the corresponding status
// http://localhost:8080/list_seats
// http://localhost:8080/list_seats?user=USERID
void list_seats(int connfd, int bufsize) {
    char buf[bufsize + 1];
    seat_t* curr = seat_header; // seat_header shared but never change, no need to protect
    int index = 0;

    while(curr != NULL && index < bufsize + strlen("%d %c,")) {
        pthread_mutex_lock(&seat_mutex);
        int length = snprintf(buf + index, bufsize - index, "%d %c,", curr->id, seat_state_to_char(curr->state));
        pthread_mutex_unlock(&seat_mutex);
        // int snprintf(char *str, size_t n, const char *format, ...);
        // write n characters of the formatted string to memory location starting from str
        // return the number of characters written

        if (length > 0)
            index = index + length; // index is the position in the buffer available to be written

        curr = curr->next; // curr->next shared but never change, no need to protect
    }

    if (index > 0)
        snprintf(buf + index - 1, bufsize - index - 1, "\n");
    else
        snprintf(buf, bufsize, "No seats is found\n\n");

    char *ok_response = "HTTP/1.0 200 OK\r\n"\
                        "Content-type: text/html\r\n\r\n";
    writenbytes(connfd, ok_response, strlen(ok_response));
    writenbytes(connfd, buf, strlen(buf));
    close(connfd);

    return;
}

// client want to reserve the seat, put the seat to the pending state
// http://localhost:8080/view_seat?user=USERID&seat=SEAT_NUM
void view_seat(int connfd, int bufsize, int seat_id, int customer_id) {
    char buf[bufsize + 1];
    seat_t* curr = seat_header;

    while(curr != NULL) {// keep looping until the end of the linked list
        // curr->id shared but never change, no need to protect
        if(curr->id == seat_id) { // current id seat id client wants

            pthread_mutex_lock(&seat_mutex);
            if (curr->state == AVAILABLE || (curr->state == PENDING && curr->customer_id == customer_id)) {
                snprintf(buf, bufsize, "Confirm seat: %d %c ?\n\n", curr->id, seat_state_to_char(curr->state));
                curr->state = PENDING;
                curr->customer_id = customer_id;
                pthread_mutex_unlock(&seat_mutex);
            }
            else if (curr->state == PENDING) { // the seat is pending and the user is not the one who reserved the seat
                pthread_mutex_unlock(&seat_mutex);

                sem_wait(standby_mutex);
                if (standby_size == STANDBY_SIZE) // standby list is full
                    snprintf(buf, bufsize, "The standby list is full. We cannot make you to the list.\n\n");
                else if (!isDuplicate(seat_id, customer_id, standby_header)) { // if the customer did not make the same request before
                    add2StandbyList(seat_id, customer_id, &standby_header, &standby_size); // insert the user to the standby list
                    snprintf(buf, bufsize, "Seat unavailable\n\n");
                }
                else
                    snprintf(buf, bufsize, "Please do not make multiple request to the same seat\n\n");
                sem_post(standby_mutex);
            }
            else {// the seat is occupied
                pthread_mutex_unlock(&seat_mutex);
                snprintf(buf, bufsize, "Seat unavailable\n\n");
            }

            char *ok_response = "HTTP/1.0 200 OK\r\n"\
                                "Content-type: text/html\r\n\r\n";
            writenbytes(connfd, ok_response, strlen(ok_response));
            writenbytes(connfd, buf, strlen(buf));
            close(connfd);

            return;
        }

        curr = curr->next;
    }

    snprintf(buf, bufsize, "Requested seat not found\n\n");

    char *ok_response = "HTTP/1.0 200 OK\r\n"\
                        "Content-type: text/html\r\n\r\n";
    writenbytes(connfd, ok_response, strlen(ok_response));
    writenbytes(connfd, buf, strlen(buf));
    close(connfd);

    return;
}

// http://localhost:8080/confirm?user=USERID&seat=SEAT_NUM
void confirm_seat(int connfd, int bufsize, int seat_id, int customer_id) {
    int found = 0;
    int remove = 0;
    char buf[bufsize + 1];

    seat_t* curr = seat_header;

    while(curr != NULL) {// search for the seat by using the id
        if(curr->id == seat_id) {
            found = 1;

            pthread_mutex_lock(&seat_mutex);
            // if the customer has reserved the seat through view seat
            if(curr->state == PENDING && curr->customer_id == customer_id) {
                snprintf(buf, bufsize, "Seat confirmed: %d %c\n\n", curr->id, seat_state_to_char(curr->state));
                curr->state = OCCUPIED;
                pthread_mutex_unlock(&seat_mutex);
                remove = 1;
            }
            else if(curr->customer_id != customer_id) {
                pthread_mutex_unlock(&seat_mutex);
                snprintf(buf, bufsize, "Permission denied - seat held by another user\n\n");
            }
            else if(curr->state != PENDING) {
                pthread_mutex_unlock(&seat_mutex);
                snprintf(buf, bufsize, "No pending request\n\n");
            }
            else
                pthread_mutex_unlock(&seat_mutex);

            break;
        }
        curr = curr->next;
    }

    if (!found)
        snprintf(buf, bufsize, "Requested seat not found\n\n");
    else if (remove){ // remove all the request with the seat number from the standby list
        sem_wait(standby_mutex);
        removeFromStandbyList(seat_id, &standby_header, &standby_size);
        sem_post(standby_mutex);
    }

    char *ok_response = "HTTP/1.0 200 OK\r\n"\
                        "Content-type: text/html\r\n\r\n";
    writenbytes(connfd, ok_response, strlen(ok_response));
    writenbytes(connfd, buf, strlen(buf));
    close(connfd);

    return;
}

// can only cancel a reserved seat by a customer who has reserved the seat
// http://localhost:8080/cancel?user=USERID&seat=SEAT_NUM
void cancel(int connfd, int bufsize, int seat_id, int customer_id) {
    char buf[bufsize + 1];
    int found = 0;
    int assign = 0;
    seat_t* curr = seat_header;

    printf("Cancelling seat %d for user %d\n", seat_id, customer_id);

    while(curr != NULL) { // search for the seat
        if(curr->id == seat_id) {
            found = 1;

            pthread_mutex_lock(&seat_mutex);
            if(curr->state == PENDING && curr->customer_id == customer_id) { // the customer has reserved the seat
                snprintf(buf, bufsize, "Seat request cancelled: %d %c\n\n", curr->id, seat_state_to_char(curr->state));
                curr->state = AVAILABLE;
                curr->customer_id = -1;
                pthread_mutex_unlock(&seat_mutex);
                assign = 1;
            }
            else if(curr->customer_id != customer_id) {
                pthread_mutex_unlock(&seat_mutex);
                snprintf(buf, bufsize, "Permission denied - seat held by another user\n\n");
            }
            else if(curr->state != PENDING) {
                pthread_mutex_unlock(&seat_mutex);
                snprintf(buf, bufsize, "No pending request\n\n");
            }
            else
                pthread_mutex_unlock(&seat_mutex);

            break;
        }
        curr = curr->next;
    }

    if (!found)
        snprintf(buf, bufsize, "Seat not found\n\n");
    else if (assign){ // somebody cancel the seat so we can assign customer in the standby list to the seat (make the seat pending)
        sem_wait(standby_mutex);
        assignUser2Seat(seat_id, seat_header, &standby_header, &standby_size);
        sem_post(standby_mutex);
    }
    
    char *ok_response = "HTTP/1.0 200 OK\r\n"\
                        "Content-type: text/html\r\n\r\n";
    writenbytes(connfd, ok_response, strlen(ok_response));
    writenbytes(connfd, buf, strlen(buf));
    close(connfd);

    return;
}

// create the linked list of seat and initialize mutex
// no need to be protected because by the time it is called, only one thread
void load_seats(int number_of_seats) {
    standby_mutex = (m_sem_t*)malloc(sizeof(m_sem_t));
    sem_init(standby_mutex, 1); // use semaphore as mutex, initialize its value to 1

    seat_t* curr = NULL;
    int i;
    for(i = 0; i < number_of_seats; i++) {
        seat_t* temp = (seat_t*) malloc(sizeof(seat_t));
        temp->id = i;
        temp->customer_id = -1;
        temp->state = AVAILABLE;
        temp->next = NULL;
        
        if (seat_header == NULL)
            seat_header = temp;
        else
            curr-> next = temp;
        curr = temp;
    }
}

// free the allocated linked list of seats
// only the main thread can unload the seat when the server is shutdown, no protection
void unload_seats() {
    pthread_mutex_destroy(&(standby_mutex->mutex));
    pthread_cond_destroy(&(standby_mutex->cond));
    free(standby_mutex);
    emptyStandbyList(&standby_header);

    seat_t* curr = seat_header;
    while(curr != NULL) {
        seat_t* temp = curr;
        curr = curr->next;
        free(temp);
    }
}

// convert a state of a seat to a character
char seat_state_to_char(seat_state_t state)
{
    switch(state)
    {
        case AVAILABLE:
            return 'A';
        case PENDING:
            return 'P';
        case OCCUPIED:
            return 'O';
    }

    return '?';
}
